using System;
using System.Diagnostics.SymbolStore;
using System.Runtime.InteropServices;
using ACS.Debugger.Core.Impl;
using ACS.Debugger.Core.Interfaces;
using ACS.Debugger.Core.Metadata;
using ACS.Debugger.Core.NativeMethods;
using ProgramDebugDatabase;
using ProgramDebugDatabase.Wrappers;
using ACS.Debugger.DebugInfo;


namespace ACS.Debugger.Core.Wrappers
{
    public class Debugger
    {   MainInfoClass Inf = new MainInfoClass();//*************************

        private ManagedCallback _callback;

        public static Debugger Instance;
        private SymBinder _binder;

        public string ModuleName
        {
            get { return _moduleName; }
            set { _moduleName = value; }
        }

        private string _moduleName;

        public SymBinder Binder
        {
            get
            {
                if (_binder == null)
                {
                    _binder = new SymBinder();
                }
                return _binder;
            }
        }

        ICorDebugModule Module
        {
            get
            {
                return _callback.Module;
            }
        }

        ICorDebugProcess Process
        {
            get
            {
                return _callback.Process;
            }
        }

        public void StartAndSetBreakPoint()
        {
            Inf.DeserializeModul("DebugInfo.xml");//********************************************

            Instance = this;

            // create debugger object.
            ICorDebug debug = Debugging.GetDebugger();

            // initialize debugger object.
            debug.Initialize();

            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
            STARTUPINFO si = new STARTUPINFO();

            si.cb = Marshal.SizeOf(si);
            si.hStdInput = new Microsoft.Win32.SafeHandles.SafeFileHandle(new IntPtr(0), false);
            si.hStdOutput = new Microsoft.Win32.SafeHandles.SafeFileHandle(new IntPtr(0), false);
            si.hStdError = new Microsoft.Win32.SafeHandles.SafeFileHandle(new IntPtr(0), false);

            _callback = new ManagedCallback();

            debug.SetManagedHandler(_callback);
            System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                ICorDebugProcess process;


                debug.CreateProcess(
                    @"C:\kurs\ACS.SampleCode\bin\Debug\acs.samplecode.exe", //lpApplicationName,
                    @"C:\kurs\ACS.SampleCode\bin\Debug\acs.samplecode.exe", //lpCommandLine,
                    //@"..\..\..\ACS.SampleCode\bin\Debug\acs.samplecode.exe", //lpCommandLine,
                    null, //lpProcessAttributes,
                    null, //lpThreadAttributes,
                    1, //bInheritHandles,
                    0, //CreateProcessFlags.CREATE_NEW_CONSOLE, //dwCreationFlags, uint
                    new IntPtr(0), //lpEnvironment,
                    @"C:\kurs\ACS.SampleCode\bin\Debug",
                    si, //lpStartupInfo,
                    pi, //lpProcessInformation,
                    CorDebugCreateProcessFlags.DEBUG_NO_SPECIAL_OPTIONS,
                    out process
                    );

                _callback.Process = process;
                //process.
                Debugging.CloseHandle(pi.hProcess);
                Debugging.CloseHandle(pi.hThread);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }

            while (true)
            {
                Console.ReadLine();
            }
        }

        public static  ISymbolReader readerforpos;
        
        [System.Security.Permissions.SecurityPermission(
            System.Security.Permissions.SecurityAction.Demand,
            Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
        private static ISymbolReader GetSymbolReaderForFile(SymBinder binder, string pathModule, string searchPath)
        {
            // First create the Metadata dispenser.
            IMetaDataDispenserEx pDispenser = new IMetaDataDispenserEx();
            // Now open an Importer on the given filename. We'll end up passing this importer straight
            // through to the Binder.
            Guid importerIID = new Guid(RCWHelper.IID_IMetaDataImport);
            IntPtr pImporter = IntPtr.Zero;
            pDispenser.OpenScope(pathModule, COR_OPEN_FLAGS.ofRead, ref importerIID, out pImporter);
            ISymbolReader symReader = null;
            try
            {
                //IMetaDataImport mdImport = RCWHelper.GetInterfaceFromIUnknown<IMetaDataImport>(pImporter);
                symReader = binder.GetReader(pImporter, pathModule, searchPath);
                readerforpos = symReader;
            }
            finally
            {
                if (pImporter != IntPtr.Zero) Marshal.Release(pImporter);
            }

            return symReader;
        }

        public void SetBreakpoint(int i)
        {
            if (Inf.IsPossibleBPset(i))//*****************************************
            {
                Guid guid = typeof (IMetadataImport).GUID;
                object metadataObject;
                Module.GetMetaDataInterface(ref guid, out metadataObject);
                IMetadataImport import = metadataObject as IMetadataImport;
                ISymbolReader reader = GetSymbolReaderForFile(Binder, ModuleName, null);
                ISymbolDocument[] documents = reader.GetDocuments();
                ISymbolDocument document = documents[0];
                ISymbolMethod method = reader.GetMethodFromDocumentPosition(document, i, 0);
                SymbolToken token = method.Token;
                int offset = method.GetOffset(document, i, 0);

                ICorDebugFunction function;

                Module.GetFunctionFromToken(token, out function);

                ICorDebugCode ilCode;
                function.GetILCode(out ilCode);

                ICorDebugFunctionBreakpoint breakpoint;
                ilCode.CreateBreakpoint((uint) offset, out breakpoint);
            }
        }

        ICorDebugThread m_currentThread;

        public int GetSourceLine(int insPointer)
        {
            ICorDebugFrame runtimeFrame;
            SymbolToken token;
            ISymbolMethod symbolicMeth;
            int LineNumber = -1;

            try
            {
                m_currentThread.GetActiveFrame( out runtimeFrame );

                runtimeFrame.GetFunctionToken( out token );

                symbolicMeth = readerforpos.GetMethod( token );

                int sequencePC;
                sequencePC = symbolicMeth.SequencePointCount;

                int[] sequencePointoffsets = new int[sequencePC];
                int[] sequencePointlines = new int[sequencePC];
                ISymbolDocument[] docs = new ISymbolDocument[sequencePC];
                int[] sequencePointcolumns = new int[sequencePC];
                int[] sequencePointendLines = new int[sequencePC];
                int[] sequencePointendColumns = new int[sequencePC];
               
                symbolicMeth.GetSequencePoints(sequencePointoffsets,    //offsets
                                               docs,                    //documets   
                                               sequencePointlines,      //lines
                                               sequencePointcolumns,    //columns   
                                               sequencePointendLines,   //endlines  
                                               sequencePointendColumns);//endcolumns

                int currentSequencePoint = 0;
                bool lineMatched = false;

                while (currentSequencePoint < sequencePC)
                {
                    if (insPointer == sequencePointoffsets[ currentSequencePoint ])
                    {
                        lineMatched = true;
                        break;
                    }

                    currentSequencePoint++;
                }

                if (lineMatched)
                {
                    LineNumber = sequencePointlines[ currentSequencePoint ];
                }
            }
            
            catch(Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }
            
            return LineNumber;
        }

        public void SetCurrentThread(ICorDebugThread currentThread)
        {
            m_currentThread = currentThread;
        }
    }
}
